#include "xlcomm/base/date_time.h"

#include <string.h>
#include <time.h>

#include "xlcomm/base/logger.h"

#ifdef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif  // _WIN32

namespace xlcomm {

CommTime DateTime::Now() {
#ifdef _WIN32
  CommTime comm_time;
  MemZero(&comm_time, sizeof comm_time);
  SYSTEMTIME sys_time;
  ::GetLocalTime(&sys_time);
  comm_time.year = sys_time.wYear;
  comm_time.month = sys_time.wMonth;
  comm_time.day = sys_time.wDay;
  comm_time.week = sys_time.wDayOfWeek;
  comm_time.hour = sys_time.wHour;
  comm_time.minute = sys_time.wMinute;
  comm_time.second = sys_time.wSecond;
  comm_time.usec = sys_time.wMilliseconds * 1000;
  return comm_time;
#else
  struct timeval tv;
  XLCOMM_ASSERT(gettimeofday(&tv, NULL) == 0);
  return TimevalToCommTime(tv);
#endif  // _WIN32
}

CommTime DateTime::TimevalToCommTime(const struct timeval& tv) {
#ifdef _WIN32
  // TODO:
#else
  CommTime comm_time;
  MemZero(&comm_time, sizeof comm_time);
  struct tm* tm_time = localtime(&tv.tv_sec);
  comm_time.year = tm_time->tm_year + 1900;
  comm_time.month = tm_time->tm_mon + 1;
  comm_time.day = tm_time->tm_mday;
  comm_time.week = tm_time->tm_wday;
  comm_time.hour = tm_time->tm_hour;
  comm_time.minute = tm_time->tm_min;
  comm_time.second = tm_time->tm_sec;
  comm_time.usec = static_cast<int>(tv.tv_usec);
  return comm_time;
#endif  // _WIN32
}

std::string DateTime::Format(const CommTime& time) {
  char str[32];
  MemZero(str, sizeof str);
#ifdef _WIN32
  snprintf(str, 32, "%d%02d%02d-%02d:%02d:%02d.%03d", time.year, time.month,
           time.day, time.hour, time.minute, time.second, time.usec / 1000);
#else
  snprintf(str, 32, "%d%02d%02d-%02d:%02d:%02d.%06d", time.year, time.month,
           time.day, time.hour, time.minute, time.second, time.usec);
#endif  // _WIN32
  return str;
}

bool DateTime::Equal(const CommTime& time1, const CommTime& time2) {
  if (time1.year == time2.year && time1.month == time2.month &&
      time1.day == time2.day && time1.hour == time2.hour &&
      time1.minute == time2.minute && time1.second == time2.second)
    return true;
  else
    return false;
}

Timestamp DateTime::NowTimestamp() {
  Timestamp timestamp = 0;
  struct timeval tv;
#ifdef _WIN32
  SYSTEMTIME sys_time;
  GetLocalTime(&sys_time);
  struct tm tm_time;
  tm_time.tm_year = sys_time.wYear - 1900;
  tm_time.tm_mon = sys_time.wMonth - 1;
  tm_time.tm_mday = sys_time.wDay;
  tm_time.tm_hour = sys_time.wHour;
  tm_time.tm_min = sys_time.wMinute;
  tm_time.tm_sec = sys_time.wSecond;
  tm_time.tm_isdst = -1;
  tv.tv_sec = mktime(&tm_time);
  tv.tv_usec = sys_time.wMilliseconds * 1000;
#else
  XLCOMM_ASSERT(gettimeofday(&tv, NULL) == 0);
#endif  // _WIN32
  timestamp = tv.tv_sec * kMicroSecondsPerSecond + tv.tv_usec;
  return timestamp;
}

CommTime DateTime::ToCommTime(Timestamp ts) {
  struct timeval tv;
  tv.tv_sec = ts / kMicroSecondsPerSecond;
  tv.tv_usec = ts % kMicroSecondsPerSecond;
  return TimevalToCommTime(tv);
}

std::string DateTime::Format(Timestamp ts) {
  CommTime time = ToCommTime(ts);
  return Format(time);
}

Timestamp DateTime::AddSeconds(Timestamp ts, double seconds) {
  return ts + static_cast<Timestamp>(seconds * kMicroSecondsPerSecond);
}

////////////////////Timing///////////////////////
Timing::Timing() : start_timestamp_(0) {}

void Timing::Start() {
#ifdef _WIN32
  LARGE_INTEGER counter;
  XLCOMM_ASSERT(QueryPerformanceCounter(&counter));
  start_timestamp_ = counter.QuadPart;
#else
  struct timeval tv;
  XLCOMM_ASSERT(gettimeofday(&tv, NULL) == 0);
  start_timestamp_ = tv.tv_sec * kMicroSecondsPerSecond + tv.tv_usec;
#endif  // _WIN32
}

int64_t Timing::Stop() {
  int64_t stop_timestamp;
#ifdef _WIN32
  LARGE_INTEGER counter;
  XLCOMM_ASSERT(QueryPerformanceCounter(&counter));
  stop_timestamp = counter.QuadPart;

  LARGE_INTEGER frequency;
  XLCOMM_ASSERT(QueryPerformanceFrequency(&frequency));
  int64_t time_frequency = frequency.QuadPart;
  XLCOMM_ASSERT(time_frequency != 0);
  return (stop_timestamp - start_timestamp_) * kMicroSecondsPerSecond /
         time_frequency;
#else
  struct timeval tv;
  XLCOMM_ASSERT(gettimeofday(&tv, NULL) == 0);
  stop_timestamp = tv.tv_sec * kMicroSecondsPerSecond + tv.tv_usec;
  return stop_timestamp - start_timestamp_;
#endif  // _WIN32
}

}  // namespace xlcomm